package room;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.TreeMap;

import common.Base;
//import common.Base;
import common.ErrCode;
import user.BetData;
import user.User;
import user.UserMgr;



//房间状态
enum RoomStatus {
    // rsInit, //初始化
    RS_IDLE, // 空闲
    RS_BET, // 押注
    RS_RESULT, // 结算中
    RS_RESULT_CP, // 结算结束展示
}

/**
 * 房间类
 */
public class Room {

    int mId; // 房间id
    int mStatusCnt; // 状态计时器

    Map<Integer, User> mUsers = new HashMap<>(); // 角色(包括机器人)id-->
    RoomStatus mStatus = RoomStatus.RS_IDLE; // 状态
    Map<Integer, BetData> mBets = new TreeMap<>(); // 押注区索引(0开始)-->数量

    // 不同状态的时间长(秒)
    static final int STATUS_TIMEL_IDLE = 5; // 空闲(初始化准备)
    static final int STATUS_TIMEL_BET = 11; // 押注
    static final int STATUS_TIMEL_RESULT = 3; // 开奖结算
    static final int STATUS_TIMEL_RESULT_CP = 5; // 结算出炉 展示并广播结果

    // static final int MAX_USER_NUM = 30; //房间最大人数
    // 押注积分单位
    static final int[] ARR_BETSCORES = { 10, 50, 100, 1000, 5000, 10000 };

    // 押注区倍率1~8
    static final int[] ARR_BETRATIO = { 40, 25, 15, 10, 5, 5, 5, 5 };

    /**
     *
     * @param id     房间id 由room mgr分配
     * @param robotN 机器人初始化数
     */
    public Room(int id, int robotN) {
        mId = id;

        // 构造机器人
        for (int i = 0; i < robotN; i++) {
            User u = createRobot();
            if (u != null) {
                // 加入房间
                onEnter(u);
            }
        }

        // 初始化所有区
        for (int idx = 0; idx < ARR_BETRATIO.length; idx++) {
            mBets.put(idx, new BetData(idx, ARR_BETRATIO[idx]));
        }
    }

    public int getID() {
        return mId;
    }

    /**
     * 构造机器人
     *
     * @return
     */
    public User createRobot() {
        UserMgr um = UserMgr.getInstance();
        if (um.getUser(Base.getRobotID()) != null) {
            // 不应该走到这里
            System.err.printf("********************** room create robot err, has exist:%d\n", Base.getRobotID());
            return null;
        }

        User u = new User(um, Base.getRobotID(), 0, true);
        Base.addRobotID(); // 自增id
        return u;
    }

    /**
     * 进入房间
     *
     * @param u 角色
     * @return
     */
    public int onEnter(User u) {
        User o = mUsers.get(u.getID());
        if (o != null) {
            System.out.printf("room enter:%d, user:%d has in room\n", mId, u.getID());
            return ErrCode.ERR_ENTERROOM_HASIN;
        }

        mUsers.put(u.getID(), u);
        u.setRoom(this.mId);
        System.out.printf("room enter:%d, user:%d ok\n", mId, u.getID());

        // 移除一个机器人
        if (!u.isRobot()) {
            User robot = randRobot();
            onLeave(robot);
        }
        return ErrCode.ERR_OK;
    }

    /**
     * 随机一个机器人
     *
     * @return
     */
    User randRobot() {
        ArrayList<User> robots = getUsers(2);
        if (robots.size() == 0) {
            return null;
        }
        int idx = new Random().nextInt(1000) % robots.size();
        return robots.get(idx);
    }

    /**
     * 获取角色集
     *
     * @param fg 0=全部 1=真人 2=机器人
     * @return
     */
    ArrayList<User> getUsers(int fg) {
        ArrayList<User> o = new ArrayList<>();
        mUsers.forEach((uid, u) -> {
            if ((fg == 0) || (fg == 2 && u.isRobot()) || (fg == 1 && !u.isRobot())) {
                o.add(u);
            }
        });
        return o;
    }

    /**
     * 离开房间
     *
     * @param u
     * @return
     */
    public int onLeave(User u) {
        User o = mUsers.get(u.getID());
        if (o == null) {
            System.err.printf("room leave:%d, user:%d not in\n", mId, u.getID());
            return ErrCode.ERR_LEAVEROOM_NOTIN;
        }

        mUsers.remove(u.getID());
        u.setRoom(0);

        System.out.printf("room leave:%d, user:%d ok\n", mId, u.getID());
        return ErrCode.ERR_OK;
    }

    /**
     * 房间押注
     *
     * @param u     角色
     * @param idx   押注区索引
     * @param score 积分
     * @return
     */
    public int onBet(User u, int idx, int score) {
        User o = mUsers.get(u.getID());
        if (o == null) {
            System.err.printf("room bet:%d, user:%d not in\n", score, u.getID());
            return ErrCode.ERR_BET_NOTINROOM;
        }

        if (!check(u.getID(), idx, score)) {
            // System.err.printf("room bet:%d, user:%d check failed\n", score, u.getID());
            return ErrCode.ERR_BET_SCOREINVALID;
        }

        if (!u.bet(idx, ARR_BETRATIO[idx], score)) {
            return ErrCode.ERR_BET_USER_FAILED;
        }

        if (!bet(idx, score)) {
            return ErrCode.ERR_BET_ROOM_FAILED;
        }

        // todo 转化json消息
        String msg = "{cmd:bet, idx:${idx}, score:${score}}";
        broadCastMsg(msg);

        return ErrCode.ERR_OK;
    }

    /**
     * 记录房间押注区数据
     *
     * @param score
     */
    private boolean bet(int idx, int score) {
        BetData bd = mBets.computeIfAbsent(idx, k -> new BetData(idx, 0));
        if (bd == null || bd.getRatio() == 0) {
            // 不应该走到这里
            System.err.println("*********************** room bet, err");
            return false;
        }
        bd.add(score);
        return true;
    }

    /**
     * 机器人押注
     */
    private void robotBet() {
        // 随机选取20%~%50%
        ArrayList<User> users = getUsers(2);
        Collections.shuffle(users, new Random());

        int r = new Random().nextInt(1000) % 30 + 20; // 押注人数权重
        int realN = users.size() * r / 100; // 实际押注人数
        System.out.printf("room robot bet, realN:%d\n", realN);

        for (int i = 0; i < realN; i++) {
            int area = new Random().nextInt(1000) % ARR_BETRATIO.length; // 区
            int score = randBetScore(); // 积分
            users.get(i).bet(area, ARR_BETRATIO[area], score);
        }
    }

    /**
     * 返回房间角色id集
     *
     * @param fg 0=全部 1=真人 2=机器人
     * @return
     */
    ArrayList<Integer> getUids(int fg) {
        ArrayList<Integer> o = new ArrayList<>();
        mUsers.forEach((uid, u) -> {
            if ((fg == 0) || (fg == 2 && u.isRobot()) || (fg == 1 && !u.isRobot())) {
                o.add(u.getID());
            }
        });
        return o;
    }

    /**
     * 房间广播消息
     *
     * @param msg 消息体 protobuf或者json结构
     */
    void broadCastMsg(String msg) {
        mUsers.forEach((id, u) -> {
            // todo:替换掉 用新接口
            if (!u.isRobot()) {
                u.send(msg);
            }
        });
    }

    /**
     * 积分合法性
     *
     * @param idx   押注区索引
     * @param score
     * @return
     */
    public static boolean check(int uid, int idx, int score) {
        if (idx < 0 || idx >= ARR_BETRATIO.length) {
            System.err.printf("room bet check, user:%d idx:%d invalid\n", uid, idx);
            return false;
        }

        for (int i = 0; i < ARR_BETSCORES.length; i++) {
            if (ARR_BETSCORES[i] == score) {
                return true;
            }
        }
        System.err.printf("room bet check, user:%d score:%d invalid\n", uid, score);
        return false;
    }

    /**
     * 房间状态轮询(1秒)
     */
    public void onTimer() {
        System.out.printf("room ontimer:%d, rmstatus:%s, statusCnt:%d\n", mId, mStatus, mStatusCnt);

        mStatusCnt++;
        switch (mStatus) {
            case RS_IDLE:
                if (mStatusCnt >= STATUS_TIMEL_IDLE) {
                    mStatusCnt = 0;
                    mStatus = RoomStatus.RS_BET;
                }
                break;

            case RS_BET: // 押注
                if (mStatusCnt >= STATUS_TIMEL_BET) {
                    mStatusCnt = 0;
                    mStatus = RoomStatus.RS_RESULT;
                }
                // 机器人 模拟押注
                robotBet();
                break;

            case RS_RESULT: // 结算中 其实是不结算的
                if (mStatusCnt >= STATUS_TIMEL_RESULT) {
                    mStatusCnt = 0;
                    mStatus = RoomStatus.RS_RESULT_CP;
                }
                break;

            case RS_RESULT_CP:
                if (mStatusCnt >= STATUS_TIMEL_RESULT_CP) {
                    // 要到进入结算展示的时候 才结算
                    result();

                    mStatusCnt = 0;
                    mStatus = RoomStatus.RS_IDLE;
                }
                break;

            default:
                break;
        }

        // 客户端最好自己本地自动处理状态机流水 不需要服务器同步状态机信息
        // broadCastMsg(null);
    }

    /**
     * 结算 1.如果压的积分数为最大即是8,则按压积分的陪数*用户压的积分(包括机器人),进行大到小排序,取最后三名中奖
     * 2.如果压的积分数小于8大于3,则空闲的积分随机选一个中奖,按压积分的陪数*用户压的积分(包括机器人),进行大到小排序,取最后两名中奖
     * 3.如果压的积分数小于等于3,则空闲的积分随机选一个中奖,按压积分的陪数*用户压的积分(包括机器人),进行大到小排序,取最后一名中奖
     */
    public void result() {
        // int realBetN = 0; //实际有押注的区
        ArrayList<BetData> betDatas = new ArrayList<>(); // 押注区数据>0 数组格式
        ArrayList<BetData> betDatas0 = new ArrayList<>(); // 押注区数据=0(空闲) 数组格式

        for (Integer k : mBets.keySet()) {
            BetData o = mBets.get(k);
            o.calcScore(); // 计算
            if (o.getScore() > 0) {
                betDatas.add(o);
            } else {
                betDatas0.add(o);
            }
        }

        // 降序
        Collections.sort(betDatas, (o1, o2) -> Integer.compare(o2.getScore(), o1.getScore()));
        /*
         * System.out.printf("\nroom result:%d, betDatas:\n", mId);
         * for (BetData e : betDatas) {
         * System.out.printf("------ %s\n", e.info());
         * }
         */

        // 中奖区
        ArrayList<BetData> winBet = new ArrayList<>();
        if (betDatas.size() == mBets.size()) { // 情况11111
            for (int i = 1; i < 4; i++) {
                winBet.add(betDatas.get(betDatas.size() - i));
            }
        } else if (betDatas.size() > 3) { // 情况22222
            int i = new Random().nextInt(1000) % (betDatas0.size());
            winBet.add(betDatas0.get(i));

            // 再来一区 先删除
            if (!betDatas0.isEmpty()) {
                betDatas0.remove(i);
                i = new Random().nextInt(1000) % (betDatas0.size());
                winBet.add(betDatas0.get(i));
            }
        } else { // 情况33333
            int i = new Random().nextInt(1000) % (betDatas0.size());
            winBet.add(betDatas0.get(i));
        }

        // 输出中奖区信息
        System.out.printf("\nroom result:%d, win bets\n", mId);
        for (int idx = 0; idx < winBet.size(); idx++) {
            BetData e = winBet.get(idx);
            System.out.printf("------ %s\n", e.info());
        }

        // 广播中奖信息给玩家(修改为json)
        String msg = "{cmd:room_result, wins:[";
        for (BetData e : winBet) {
            msg += Integer.toString(e.getIdx()) + ",";
        }
        msg += "]";
        broadCastMsg(msg);
    }

    // ===============================================================================
    public void showStatus() {
        System.out.println(RoomStatus.RS_BET);
        RoomStatus rs = RoomStatus.valueOf("RS_IDLE");
        System.out.println(rs);
        // 枚举格式化 是个字符串
        System.out.printf("RoomStatus.RS_IDLE:%s\n", RoomStatus.RS_IDLE);
    }

    public void showBet() {
        System.out.printf("room:%d bets:%d\n", mId, mBets.size());
        mBets.forEach((k, v) -> {
            // a
            System.out.printf("--------- idx:%d, data:%s\n", k, v.info());
        });
    }

    public static int randBetScore() {
        int i = new Random().nextInt(1000) % (ARR_BETSCORES.length);
        return ARR_BETSCORES[i];
    }

    public static int[] getArrBetRatio() {
        return ARR_BETRATIO;
    }

}
